/**************************************************
 * Project Name         [JavaEE_XML]
 * File Name            [JaxpDOMTree.java]
 * Creation Date        [08-14-2014]
 * 
 * Copyright© ge.y.yang@gmail.com All Rights Reserved
 * 
 * Work hard, play harder, think big and keep fit
 **************************************************/
package pkg_01_jaxp;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.concurrent.ExecutionException;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.TreeModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

/**
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
public class JaxpDOMTree {

	/**
	 * 测试方法
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				JFrame frame = new DOMTreeFrame();
				frame.setVisible(true);
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			}
		});
	}
}

/**
 * DOM Tree Frame
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
class DOMTreeFrame extends JFrame {

	private static final long serialVersionUID = -3670577950316066066L;

	private DocumentBuilder builder = null;

	private static final int DEFAULT_WIDTH = 400;
	private static final int DEFAULT_HEIGHT = 400;

	public DOMTreeFrame() {
		setTitle("DOM Tree Parser");
		this.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		setLocationByPlatform(true);

		JMenuBar menuBar = new JMenuBar();
		setJMenuBar(menuBar);

		JMenu menu = new JMenu("File");
		JMenuItem openItem = new JMenuItem("Open");
		menu.add(openItem);
		openItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				openFile();
			}
		});

		JMenuItem exitItem = new JMenuItem("Exit");
		menu.add(exitItem);
		exitItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				System.exit(0);
			}
		});

		menuBar.add(menu);
	}

	/**
	 * 打开文件
	 */
	public void openFile() {
		JFileChooser chooser = new JFileChooser();
		chooser.setSelectedFile(new File("."));

		// 设置文件过滤器
		chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
			@Override
			public boolean accept(File file) {
				return file.isDirectory()
						|| file.getName().toLowerCase().endsWith(".xml");
			}

			@Override
			public String getDescription() {
				return "xml files";
			}
		});

		int result = chooser.showOpenDialog(this);
		if (result != JFileChooser.APPROVE_OPTION) {
			return;
		}
		final File file = chooser.getSelectedFile();
		new SwingWorker<Document, Void>() {

			@Override
			protected Document doInBackground() throws Exception {
				if (builder == null) {
					// 获得DocumentBuilderFactory
					DocumentBuilderFactory factory = DocumentBuilderFactory
							.newInstance();
					// 获得DocumentBuilder
					builder = factory.newDocumentBuilder();
				}
				// 解析文件
				return builder.parse(file);
			}

			@Override
			protected void done() {
				try {
					Document doc = get();
					JTree tree = new JTree(new DOMTreeModel(doc));
					tree.setCellRenderer(new DOMTreeCellRenderer());

					setContentPane(new JScrollPane(tree));
					validate();
				} catch (InterruptedException e) {
					JOptionPane.showMessageDialog(DOMTreeFrame.this, e);
				} catch (ExecutionException e) {
					JOptionPane.showMessageDialog(DOMTreeFrame.this, e);
				}

			}
		}.execute();
	}

}

/**
 * DOM Tree Model
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
class DOMTreeModel implements TreeModel {

	private Document doc = null;

	public DOMTreeModel(Document doc) {
		this.doc = doc;
	}

	/**
	 * 获得根节点
	 */
	@Override
	public Object getRoot() {
		return doc.getDocumentElement();
	}

	/**
	 * 获得指定索引的子节点
	 */
	@Override
	public Object getChild(Object parent, int index) {
		Node node = (Node) parent;
		return node.getChildNodes().item(index);
	}

	/**
	 * 获得子节点数目
	 */
	@Override
	public int getChildCount(Object parent) {
		return ((Node) parent).getChildNodes().getLength();
	}

	/**
	 * 是否为叶子节点
	 */
	@Override
	public boolean isLeaf(Object node) {
		return getChildCount(node) == 0;
	}

	/**
	 * 获得子节点索引
	 */
	@Override
	public int getIndexOfChild(Object parent, Object child) {
		Node node = (Node) parent;
		NodeList list = node.getChildNodes();
		for (int i = 0; i < list.getLength(); i++) {
			if (getChild(node, i) == child) {
				return i;
			}
		}
		return 0;
	}

	@Override
	public void valueForPathChanged(TreePath path, Object newValue) {
	}

	@Override
	public void addTreeModelListener(TreeModelListener l) {
	}

	@Override
	public void removeTreeModelListener(TreeModelListener l) {
	}
}

/**
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
class DOMTreeCellRenderer extends DefaultTreeCellRenderer {

	private static final long serialVersionUID = 1207028169030937928L;

	public static JPanel createElementPanel(Element element) {
		JPanel panel = new JPanel();
		panel.add(new JLabel("Element : " + element.getTagName()));

		final NamedNodeMap attributesMap = element.getAttributes();
		panel.add(new JTable(new AbstractTableModel() {
			private static final long serialVersionUID = 6452665001097488806L;

			@Override
			public Object getValueAt(int rowIndex, int columnIndex) {
				return columnIndex == 0 ? attributesMap.item(rowIndex)
						.getNodeName() : attributesMap.item(rowIndex)
						.getNodeValue();
			}

			@Override
			public int getRowCount() {
				return attributesMap.getLength();
			}

			@Override
			public int getColumnCount() {
				return 2;
			}
		}));
		return panel;
	};

	public static String characterString(CharacterData node) {
		StringBuilder builder = new StringBuilder(node.getData());

		for (int i = 0; i < builder.length(); i++) {
			// 如果是回车符, 输出"\r"
			if (builder.charAt(i) == '\r') {
				builder.replace(i, i + 1, "\\r");
				i++;
				// 如果是换行符, 输出"\n"
			} else if (builder.charAt(i) == '\n') {
				builder.replace(i, i + 1, "\\n");
				i++;
				// 如果是制表符, 输出"\t"
			} else if (builder.charAt(i) == '\t') {
				builder.replace(i, i + 1, "\\t");
				i++;
			}
		}

		if (node instanceof CDATASection) {
			builder.insert(0, "CDATASection:");
		} else if (node instanceof Text) {
			builder.insert(0, "Text:");
		} else if (node instanceof Comment) {
			builder.insert(0, "Comment:");
		}

		return builder.toString();
	}

	@Override
	public Component getTreeCellRendererComponent(JTree tree, Object value,
			boolean sel, boolean expanded, boolean leaf, int row,
			boolean hasFocus) {
		Node node = (Node) value;

		if (node instanceof Element) {
			return createElementPanel((Element) node);
		}

		super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
				row, hasFocus);

		if (node instanceof CharacterData) {
			setText(characterString((CharacterData) node));
		} else {
			setText(node.getClass() + ":" + node.toString());
		}
		return this;
	}
}
